/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.example.rfidassetzone;

import com.espertech.esper.runtime.client.EPRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;

public class AssetEventGenCallable implements Callable<Boolean> {
    public static final int NUM_ZONES = 20;

    private static final Logger log = LoggerFactory.getLogger(AssetEventGenCallable.class);
    private final EPRuntime runtime;
    private final String[][] assetIds;
    private final int[][] zoneIds;
    private final Integer[] assetGroupsForThread;
    private final int ratioZoneMove;
    private final int ratioZoneSplit;

    private int numEventsSend;
    private int numZoneMoves;
    private int numZoneSplits;
    private int numSameZone;
    private Set<Integer> splitZoneGroups = new HashSet<Integer>();
    private Random random = new Random();

    private boolean shutdown;
    private boolean isGenerateZoneSplit;

    public AssetEventGenCallable(EPRuntime runtime, String[][] assetIds, int[][] zoneIds, Integer[] assetGroupsForThread, int ratioZoneMove, int ratioZoneSplit) {
        this.runtime = runtime;
        this.assetIds = assetIds;
        this.zoneIds = zoneIds;
        this.assetGroupsForThread = assetGroupsForThread;
        this.ratioZoneMove = ratioZoneMove;
        this.ratioZoneSplit = ratioZoneSplit;
        isGenerateZoneSplit = true;
    }

    public void setShutdown(boolean shutdown) {
        this.shutdown = shutdown;
    }

    public void setGenerateZoneSplit(boolean generateZoneSplit) {
        isGenerateZoneSplit = generateZoneSplit;
    }

    public boolean isGenerateZoneSplit() {
        return isGenerateZoneSplit;
    }

    public int getNumEventsSend() {
        return numEventsSend;
    }

    public Boolean call() throws Exception {
        try {
            log.info(".call Thread " + Thread.currentThread().getId() + " starting");
            while (!shutdown) {
                boolean isZoneMove = (random.nextInt() % ratioZoneMove) == 1;
                boolean isZoneSplit = (random.nextInt() % ratioZoneSplit) == 1;
                if (isZoneMove) {
                    doZoneMove();
                } else if (isZoneSplit && isGenerateZoneSplit) {
                    doZoneSplit();
                } else {
                    doSameZone();
                }
            }
            log.info(".call Thread " + Thread.currentThread().getId() + " done");
        } catch (Exception ex) {
            log.error("Error in thread " + Thread.currentThread().getId(), ex);
            return false;
        }
        return true;
    }

    private void doZoneMove() {
        // Chose among one of the groups for this thread
        int index = Math.abs(random.nextInt()) % assetGroupsForThread.length;
        int groupNum = assetGroupsForThread[index];

        // If this is a currently-split group, don't reunion
        if (splitZoneGroups.contains(groupNum)) {
            return;
        }

        // Determine zone to move to
        int newZone;
        do {
            newZone = Math.abs(random.nextInt()) % NUM_ZONES;
        }
        while (zoneIds[groupNum][0] == newZone);

        // Move all assets for this group to a new, random zone
        for (int i = 0; i < assetIds[i].length; i++) {
            zoneIds[groupNum][i] = newZone;
            LocationReport report = new LocationReport(assetIds[groupNum][i], newZone);
            runtime.getEventService().sendEventBean(report, "LocationReport");
            numEventsSend++;
        }
        numZoneMoves++;
    }

    private void doSameZone() {
        // Chose among one of the groups for this thread
        int index = Math.abs(random.nextInt()) % assetGroupsForThread.length;
        int groupNum = assetGroupsForThread[index];

        // If this is a currently-split group, don't reunion
        if (splitZoneGroups.contains(groupNum)) {
            return;
        }

        // Re-send all assets for this group as the same zone
        for (int i = 0; i < assetIds[i].length; i++) {
            LocationReport report = new LocationReport(assetIds[groupNum][i], zoneIds[groupNum][i]);
            runtime.getEventService().sendEventBean(report, "LocationReport");
            numEventsSend++;
        }
        numSameZone++;
    }

    private void doZoneSplit() {
        int groupNum;
        do {
            int index = Math.abs(random.nextInt()) % assetGroupsForThread.length;
            groupNum = assetGroupsForThread[index];
        }
        while (splitZoneGroups.contains(groupNum));
        splitZoneGroups.add(groupNum);

        // Determine zone to move to
        int oldZone = zoneIds[groupNum][0];
        int newZone;
        do {
            newZone = Math.abs(random.nextInt()) % NUM_ZONES;
        }
        while (zoneIds[groupNum][0] == newZone);

        log.info(".doZoneSplit Split group " + groupNum + " to different zones, from zone " + oldZone + " to zone " + newZone);

        // Move all assets for this group except the last asset to the new zone
        for (int i = 0; i < assetIds[i].length - 1; i++) {
            zoneIds[groupNum][i] = newZone;
            LocationReport report = new LocationReport(assetIds[groupNum][i], newZone);
            runtime.getEventService().sendEventBean(report, "LocationReport");
            numEventsSend++;
        }
        numZoneSplits++;
    }

    public int getNumZoneMoves() {
        return numZoneMoves;
    }

    public int getNumZoneSplits() {
        return numZoneSplits;
    }

    public int getNumSameZone() {
        return numSameZone;
    }

    public Set<Integer> getSplitZoneGroups() {
        return splitZoneGroups;
    }
}
